package com.xianyun.site.auth.service.filter;

import com.xianyun.common.core.constant.Constants;
import com.xianyun.common.core.constant.SecurityConstant;
import com.xianyun.common.core.utils.StringUtils;
import com.xianyun.common.core.utils.ip.IpUtils;
import com.xianyun.site.auth.common.exception.JustAuthLoginException;
import com.xianyun.site.auth.common.token.IAuthenticationToken;
import com.xkcoding.justauth.AuthRequestFactory;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author songyinyin
 * @date 2020/5/4 下午 06:20
 */
@Slf4j
public class IAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    /**权限认证回调拦截路径*/
    private static final String EXTEND_LOGIN_URL = "/open/oauth/check/**";


    private boolean postOnly = false;

    private AuthRequestFactory authRequestFactory;
    /**
     * 通过构造函数指定该 Filter 要拦截的 url 和 httpMethod
     */
    public IAuthenticationFilter() {
        // TODO: pattern 可以抽取成配置，最后通过配置文件进行修改，这样作为共用组件只需要实现一个 default，具体值可以有调用者指定
        super(new AntPathRequestMatcher(EXTEND_LOGIN_URL, null));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException{
        // 当设置该 filter 只拦截 post 请求时，符合 pattern 的非 post 请求会触发异常
        if (this.postOnly && !Constants.POST.equals(request.getMethod())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            // 1. 从请求中获取参数 用户登录扩展参数
            String extendKey = obtainExtendKey(request);
            String extendCredentials = obtainCredentials(request);
            String extendType = obtainExtendType(request);

            // 2. 封装成 Token 调用 AuthenticationManager 的 authenticate 方法，该方法中根据 Token 的类型去调用对应 Provider 的 authenticated
            IAuthenticationToken token;
            if (StringUtils.isNotEmpty(extendKey)) {
                token = new IAuthenticationToken(extendKey, extendType, extendCredentials,request);
            } else {
                // 从第三方拿到用户信息
                token = new IAuthenticationToken(obtainAuthUser(request),IpUtils.getClientIpAddr(request));
            }
            token.setRequest(request);
            this.setDetails(request, token);
            // 3. 返回 authenticated 方法的返回值
            return this.getAuthenticationManager().authenticate(token);
        }
    }

    /**
     * 获取扩展登录extendKey，可以是用户名、手机号、邮箱等，根据业务需要去扩展
     */
    protected String obtainExtendKey(HttpServletRequest request) {
        return request.getParameter(SecurityConstant.EXTEND_KEY_PARAMETER);
    }

    /**
     * 获取扩展登录extendCredentials，可以是 密码、手机号的验证码等，根据业务需要去扩展
     */
    protected String obtainCredentials(HttpServletRequest request) {
        return request.getParameter(SecurityConstant.EXTEND_CREDENTIALS_PARAMETER);
    }

    /**
     * 获取扩展登录类型
     */
    protected String obtainExtendType(HttpServletRequest request) {
        return request.getParameter(SecurityConstant.EXTEND_TYPE_PARAMETER);
    }

    /**
     * 获取 JustAuth 登录后的用户信息
     */
    protected AuthUser obtainAuthUser(HttpServletRequest request) {
        String type = getCallbackType(request);
        AuthRequest authRequest = authRequestFactory.get(type);
        // 登录后，从第三方拿到用户信息
        AuthResponse response = authRequest.login(getCallback(request));
        log.info("【  第三方登录 」 code:{}",response.getCode());
        // 第三方登录成功
        if (response.getCode() == AuthResponseStatus.SUCCESS.getCode()) {
            return (AuthUser) response.getData();
        }
        else {
            throw  new JustAuthLoginException(response.getMsg());
        }
    }

    /**
     * 从请求中构建 AuthCallback
     */
    private AuthCallback getCallback(HttpServletRequest request) {
        return AuthCallback.builder()
            .code(request.getParameter("code"))
            .auth_code(request.getParameter("auth_code"))
            .authorization_code(request.getParameter("authorization_code"))
            .oauth_token(request.getParameter("oauth_token"))
            .state(request.getParameter("state"))
            .oauth_verifier(request.getParameter("oauth_verifier"))
            .build();
    }

    /**
     * 获取路径参数：回调类型
     */
    private String getCallbackType(HttpServletRequest request) {
        // /open/oauth/callback/code/oschina
        String uri = request.getRequestURI();
        // "/open/oauth/callback/".length()
        int common = EXTEND_LOGIN_URL.length() - 2;
        int start = uri.indexOf(EXTEND_LOGIN_URL.substring(0, common));
        if (start == -1) {
            log.warn("【JustAuth 第三方登录 response】回调类型为空，uri={}", uri);
            return null;
        }
        // 开源中国
        return uri.substring(start + common);
    }

    protected void setDetails(HttpServletRequest request, IAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public void setAuthRequestFactory(AuthRequestFactory authRequestFactory) {
        this.authRequestFactory = authRequestFactory;
    }

}
